/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.zelic.znn.network;

import com.zelic.znn.core.LearningMethod;
import com.zelic.znn.core.Network;
import com.zelic.znn.core.Layer;
import com.zelic.znn.core.TrainingExample;
import com.zelic.znn.core.TrainingSet;

/**
 *
 * @author Zelic
 */
public class BackPropagationNetwork extends Network implements LearningMethod{

    @Override
    public double trainAnEpoch(TrainingSet trainingSet) {
        int numExample = trainingSet.getNumTrainingData();
        int numInputElement = trainingSet.getNumInput();
        int numTargetElement = trainingSet.getNumOutput();
        double error = 0;
        Layer pointer;
        double[] input;
        double[] target;
        error = 0;
        for (int j = 0; j < numExample; j++) {

            pointer = first;
            input = trainingSet.getTrainingExample(j).getInput();

            //Calculate output for each layer
            while (pointer != null) {
                pointer.setInput(input);
                pointer.calculateOutput();
                input = pointer.getOutput();
                pointer = pointer.getNextLayer();
            }

            pointer = last;
            target = trainingSet.getTrainingExample(j).getOutput();

            //Calculate delta for each layer
            while (pointer != null) {
                pointer.calculateDelta(target);
                pointer = pointer.getPreviousLayer();
            }

            //Update weight for each layer
            pointer = first;
            while (pointer != null) {
                pointer.updateWeight();
                pointer = pointer.getNextLayer();
            }
            error = error + calculateError(trainingSet.getTrainingExample(j));
        } //j
        return error/(2*numExample);        
    }

    @Override
    public double trainWithLimitedEpochs(int epochs, TrainingSet trainingSet) {
        int numExample = trainingSet.getNumTrainingData();
        int numInputElement = trainingSet.getNumInput();
        int numTargetElement = trainingSet.getNumOutput();
        double error = 0;
        Layer pointer;
        double[] input;
        double[] target;
        for (int i = 0; i < epochs; i++) {
            error = 0;
            for (int j = 0; j < numExample; j++) {

                pointer = first;
                input = trainingSet.getTrainingExample(j).getInput();

                //Calculate output for each layer
                while (pointer != null) {
                    pointer.setInput(input);
                    pointer.calculateOutput();
                    input = pointer.getOutput();
                    pointer = pointer.getNextLayer();
                }

                pointer = last;
                target = trainingSet.getTrainingExample(j).getOutput();

                //Calculate delta for each layer
                while (pointer != null) {
                    pointer.calculateDelta(target);
                    pointer = pointer.getPreviousLayer();
                }

                //Update weight for each layer
                pointer = first;
                while (pointer != null) {
                    pointer.updateWeight();
                    pointer = pointer.getNextLayer();
                }
                error = error + calculateError(trainingSet.getTrainingExample(j));
            } //j

        } //i
        return error;
    }

    @Override
    public int trainWithLimitedError(double targetError, TrainingSet trainingSet) {
        double error = Double.MAX_VALUE;
        int numExample = trainingSet.getNumTrainingData();
        int numInputElement = trainingSet.getNumInput();
        int numTargetElement = trainingSet.getNumOutput();
        Layer pointer;
        double[] input;
        double[] target;
        //Count the number of epochs
        int counter = 0;

        while (error > targetError) {
            error = 0;
            counter++;
            for (int j = 0; j < numExample; j++) {

                pointer = first;
                input = trainingSet.getTrainingExample(j).getInput();

                //Calculate output for each layer
                while (pointer != null) {
                    pointer.setInput(input);
                    pointer.calculateOutput();
                    input = pointer.getOutput();
                    pointer = pointer.getNextLayer();
                }

                pointer = last;
                target = trainingSet.getTrainingExample(j).getOutput();

                //Calculate delta for each layer
                while (pointer != null) {
                    pointer.calculateDelta(target);
                    pointer = pointer.getPreviousLayer();
                }

                //Update weight for each layer
                pointer = first;
                while (pointer != null) {
                    pointer.updateWeight();
                    pointer = pointer.getNextLayer();
                }
                double temp_error = calculateError(trainingSet.getTrainingExample(j));
                //System.out.print("Epochs: "+counter+" - Current Error: "+temp_error+"\n");
                error = error + temp_error;
            }
            System.out.print("Epochs: " + counter + " - Error: " + error + "\n");
        }
        //This can be removed
        return counter;
    }

}
